package server;

import etc.SynchronizationParameters;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Observable;
import java.util.Observer;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import server.fsm.ServerContext;
import server.fsm.WaitingState;

public class SyncServer extends Observable implements Runnable, Observer {

	private Socket clientSocket = null;

	private ServerSocket serverSocket = null;

	private Boolean cancelPending = false;

	private ServerContext currentContext = null;

	public SyncServer() throws IOException {
		serverSocket = new ServerSocket( SynchronizationParameters.getLocalPort() );
	}

	public synchronized void startListening() {
		if( serverSocket == null ) return;

		while( !cancelPending ) {
			try{
				setChanged();
				notifyObservers("Listening for connections on port " + SynchronizationParameters.getLocalPort() );
				
				clientSocket = serverSocket.accept();
				currentContext = new ServerContext( new WaitingState() );
				currentContext.addObserver(this);
				
				setChanged();
				notifyObservers("Accepted new connection from "
						+ clientSocket.getInetAddress().getHostAddress() + ". Waiting for commands.");

				int chunkSize = clientSocket.getSendBufferSize()/2;
				BufferedReader in = new BufferedReader( new InputStreamReader( clientSocket.getInputStream() ) );
				BufferedWriter out = new BufferedWriter( new OutputStreamWriter( clientSocket.getOutputStream() ) );

				String requestXML, responseXML;

				do{
					requestXML = in.readLine();
					try {
						Document xmlDocument = DocumentHelper.parseText( requestXML );
						Element rootElement = xmlDocument.getRootElement();

						if( rootElement == null ) throw new Exception();

						switch( RequestType.toCommand( rootElement.getName() ) ) {
							case login:
								String username = rootElement.attributeValue( "username" );
								String password = rootElement.attributeValue( "password" );

								if( (username.length()<1) || (password.length()<1) ) throw new Exception();

								responseXML = currentContext.login(username, password);
								break;
							case logout:
								responseXML = currentContext.logout();
								break;
							case listFiles:
								responseXML = currentContext.listFiles();
								break;
							case download:
								String downloadFile = rootElement.attributeValue( "file" );
								Long fileSizeBytes = Long.parseLong( rootElement.attributeValue( "size" ) );

								if( downloadFile.length()<1 ) throw new Exception();

								responseXML = currentContext.uploadFile( downloadFile, fileSizeBytes, chunkSize );
								break;
							case upload:
								String uploadFile = rootElement.attributeValue( "file" );
								Long uploadSize = Long.parseLong( rootElement.attributeValue( "size" ) );
								Long uploadLastModified = Long.parseLong( rootElement.attributeValue( "lastModified" ) );

								if( uploadFile.length()<1 ) throw new Exception();

								responseXML = currentContext.downloadFile( uploadFile, uploadSize, uploadLastModified );
								break;
							case data:
								String base64EncodedDataString = rootElement.getText();

								responseXML = currentContext.data( base64EncodedDataString );
								break;
							case waiting:
								String waitingFile = rootElement.attributeValue( "file" );
								Long waitingBytes = Long.parseLong( rootElement.attributeValue( "size" ) );

								responseXML = currentContext.waiting( waitingFile, waitingBytes );
								break;
							case receive:
								Boolean didFail = Boolean.parseBoolean( rootElement.attributeValue( "didFail" ) );

								responseXML = currentContext.receive( didFail );
								break;
							case delete:
								String delFile = rootElement.attributeValue( "file" );

								if( delFile.length() < 1 ) throw new Exception();

								responseXML = currentContext.delete( delFile );
								break;
							default:
								throw new Exception();
						}
						
					} catch (Exception ex) {
						responseXML = "<error type='MALFORMED_XML'>Server could not parse your command.</error>";
					}
					if( responseXML != null ) {
						out.write( responseXML );
						out.newLine();
						out.flush();
					}
				}while( !clientSocket.isClosed() );
				
			} catch (IOException ex) {
				clientSocket = null;
				currentContext = null;
			}
		}
		
		try {
			serverSocket.close();
			setChanged();
			notifyObservers("Server is stopped");
		} catch (IOException ex) {}
	}

	public void stopListening() {
		this.cancelPending = true;
		try {
			Thread.sleep(100);
		} catch (Exception ex) {}
		try {
			clientSocket.close();
		} catch (Exception ex) {}
		try {
			serverSocket.close();
		} catch (Exception ex) {}
	}

	public void run() {
		startListening();
	}

	public void update(Observable o, Object arg) {
		if( arg instanceof String ) {
			setChanged();
			notifyObservers(arg);
		}
	}

}
